#include "fs.h"
#include "fs_file.h"
#include "spinlock.h"
#include "elf.h"
#include "mmap.h"
#include "proc.h"
#include "config.h"
#include "types.h"
#include "console.h"
#include "stat.h"
struct devsw devsw[NDEV];
struct {
  spinlock_t lock;
  struct fs_file file[NFILE];
} ftable;
char tempdata[1 << 22];

// Allocate a file structure.
struct fs_file*
filealloc(void)
{
  struct fs_file *f;

  acquire(&ftable.lock);
  for(f = ftable.file; f < ftable.file + NFILE; f++){
    if(f->ref == 0){
      f->ref = 1;
      release(&ftable.lock);
      return f;
    }
  }
  release(&ftable.lock);
  return 0;
}
// Allocate a file descriptor for the given file.
// Takes over file reference from caller on success.
 int
fdalloc(struct fs_file *f)
{
  int fd;
  struct proc *p = myproc();

  for(fd = 0; fd < NOFILE; fd++){
    if(p->ofsfile[fd] == 0){
      p->ofsfile[fd] = f;
      return fd;
    }
  }
  return -1;
}
//Write to file f.
//addr is a user virtual address.
int
fs_filewrite(struct fs_file *f, uint64 addr, int n)
{
	int ret = 0;

	if(f->writable == 0)
		return -1;
	
	if(f->type == FD_PIPE){
	//	ret = pipewrite(f->pipe, addr, n);
	} else if(f->type == FD_DEVICE){
		if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)
            return -1;
        either_copyin(tempdata, 1, addr, n);
		ret = devsw[f->major].write(f->minor, tempdata, n);
	} else if(f->type == FD_ENTRY){
		/*
		elock(f->ep);
		if (ewrite(f->ep, 1, addr, f->off, n) == n) {
		ret = n;
		f->off += n;
		} else {
		ret = -1;
		}
		eunlock(f->ep);
		*/
	} else {
		panic("filewrite");
	}

	return ret;
}
//Read from fs_file f.
//addr is a uva.
int
fs_fileread(struct fs_file *f, uint64 addr, int n)
{
	int r = 0;

	if(f->readable == 0)
		return -1;

	switch (f->type) {
		case FD_PIPE:
		//	r = piperead(f->pipe, addr, n);
			break;
		case FD_DEVICE:
			if(f->major < 0 || f->major >= NDEV || !devsw[f->major].read)
                return -1;
			r = devsw[f->major].read(f->minor, tempdata, n);
            either_copyout(1, addr, tempdata, n);
			break;
		case FD_ENTRY:
			elock(f->ep);
			if((r = eread(f->ep, 1, addr, f->off, n)) > 0)
				f->off += r;
			eunlock(f->ep);
			break;
		default:
		panic("fileread");
	}

	return r;
}
// Close file f.  (Decrement ref count, close when reaches 0.)
void
fs_fileclose(struct fs_file *f)
{
  struct fs_file ff;

  acquire(&ftable.lock);
  if(f->ref < 1)
    panic("fileclose");
  if(--f->ref > 0){
    release(&ftable.lock);
    return;
  }
  ff = *f;
  f->ref = 0;
  f->type = FD_NONE;
  release(&ftable.lock);

  if(ff.type == FD_PIPE){
  //  pipeclose(ff.pipe, ff.writable);
  } else if(ff.type == FD_ENTRY){
    eput(ff.ep);
  } else if (ff.type == FD_DEVICE) {

  }
}
struct fs_file *
filedup(struct fs_file *f)
{
	acquire(&ftable.lock);
	if(f->ref < 1) panic("filedup");
	f->ref++;
	release(&ftable.lock);
	return f;
}

int sys_do_exec(char *path,char **argv){
	char *path_pa = (char *) va2pa(path);
	char **argv_pa = (char **) va2pa(argv);
	for(int argc = 0; argv_pa[argc]; argc++){
		argv_pa[argc] = (char *) va2pa( argv_pa[argc]);
	}
	int ret = do_exec(path_pa,argv_pa);
	return ret;
}


uint64
sys_dup(uint64 fd)
{
	struct fs_file *f;
	int newfd;
	f = myproc()->ofsfile[fd];
	if((newfd = fdalloc(f)) < 0) return -1;
	filedup(f);
	return newfd;
}
uint64
sys_do_read(int fd,char *buf, size_t n)
{
	struct fs_file *f;
	uint64 p;
	if(fd < 0)
		return -1;
	if(fd >= NOFILE || (f = myproc()->ofsfile[fd]) == 0)
		return -1;
	return fs_fileread(f,(uint64)buf,n);
}
uint64
sys_do_write(int fd,char *buf, size_t n)
{
	struct fs_file *f;
	uint64 p;
	if(fd < 0)
		return -1;
	if(fd >= NOFILE || (f = myproc()->ofsfile[fd]) == 0)
		return -1;
	return fs_filewrite(f,(uint64)buf,n);
}



// Get metadata about file f.
// addr is a user virtual address, pointing to a struct stat.
int
filestat(struct fs_file *f, uint64 addr)
{
  struct proc *p = myproc();
  struct stat_fs st;
  
  if(f->type == FD_ENTRY){
    elock(f->ep);
    estat(f->ep, &st);
    eunlock(f->ep);
    if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0)
      return -1;
    return 0;
  }
  return -1;
}

int sys_chdir(char *path)
{
	char *name = (char *)va2pa(path);
	struct dirent *ep;
	struct proc *p = myproc();
	if((ep = ename(name)) == 0) return -1;
	elock(ep);
	if(!(ep->attribute & ATTR_DIRECTORY)){
		eunlock(ep);
		eput(ep);
		return -1;
	}
	eunlock(ep);
	eput(p->cwd);
	p->cwd = ep;
	return 0;
}

int sys_do_fstat(int fd, struct stat_fs* st){
    struct fs_file *f;
	uint64 newst;
	newst = (uint64)st;
    f = myproc()->ofsfile[fd];
    return filestat(f, newst);
}
// Is the directory dp empty except for "." and ".." ?
static int
isdirempty(struct dirent *dp)
{
  struct dirent ep;
  int count;
  int ret;
  memset(&ep, 0, sizeof(ep));
  ret = enext(dp, &ep, 2 * 32, &count);   // skip the "." and ".."
  return ret == -1;
}
/*
int sys_link(char *new,char *old)
{
	char *new_pa, *old_pa;
	char name[DIRSIZ];
	struct inode *dp,*ip;
	new_pa = (char *)va2pa(new);
	old_pa = (char *)va2pa(old);
	if((ip = namei(old_pa)) == 0) return -1;
	ilock(ip);
	if(ip->type == T_DIR){
		iunlockput(ip);
		return -1;
	}
	ip->nlink++;
	eupdate(ip);
	eunlock(ip);
	if((dp = enameparent(new_pa,name)) == 0) goto bad;
	ilock(dp);
	if(dp->dev != ip->dev || dirlink(dp,name,ip->inum) < 0){
		iunlockput(dp);
		goto bad;
	}
	iunlockput(dp);
	iput(ip);
	return 0;
bad:
	ilock(ip);
	ip->nlink--;
	iupdate(ip);
	iunlockput(ip);
	return -1;
}
int sys_unlink(char *path)
{
	char *path_pa = (char *)va2pa(path);
	char name[DIRSIZ];
	struct inode *ip, *dp;
	struct dirent de;
	uint off;
	if((dp = enameparent(path_pa,name)) == 0) return -1;
	ilock(dp);
	if(namecmp(name,".") == 0 || namecmp(name,"..") == 0) goto bad;
	if((ip = dirlookup(dp,name,&off)) == 0) goto bad;
	ilock(ip);
	if(ip->nlink < 1) panic("unlink: nlink < 1");
	if(ip->type == T_DIR && !isdirempty(ip))
	{
		iunlockput(ip);
		goto bad;
	}
	memset(&de,0,sizeof(de));
	if(writei(dp,0,(uint64)&de,off,sizeof(de)) != sizeof(de)) panic("unlink:writei");
	if(ip->type == T_DIR){
			dp->nlink--;
			iupdate(dp);
	}
	iunlockput(dp);

	ip->nlink--;
	iupdate(ip);
	iunlockput(ip);
	return 0;
bad:
	iunlockput(dp);
	return -1;
}
*/